package com.fansin.hyperledger.fabric.java;

import cn.hutool.json.JSONUtil;
import com.fansin.hyperledger.fabric.java.config.FabricConfiguration;
import com.fansin.hyperledger.fabric.java.config.Util;
import com.fansin.hyperledger.fabric.java.store.SampleStore;
import com.fansin.hyperledger.fabric.java.store.SampleUser;
import lombok.extern.slf4j.Slf4j;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.exception.ChaincodeCollectionConfigurationException;
import org.hyperledger.fabric.sdk.exception.CryptoException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.hyperledger.fabric.sdk.exception.TransactionException;
import org.hyperledger.fabric.sdk.helper.Config;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric_ca.sdk.EnrollmentRequest;
import org.hyperledger.fabric_ca.sdk.HFCAClient;
import org.hyperledger.fabric_ca.sdk.RegistrationRequest;
import org.hyperledger.fabric_ca.sdk.exception.InvalidArgumentException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.fabric.sdk.Channel.PeerOptions.createPeerOptions;

/**
 * <p>Title: FabricPrivateDataController</p>
 * <p>Description: </p>
 *
 * @author zhaofeng
 * @version 1.0
 * @date 18-8-30
 */
@RestController("fabric")
@Slf4j
public class FabricPrivateDataController {

    @Resource
    private FabricConfiguration configuration;

    @RequestMapping("config")
    public String listConfig(){
        return JSONUtil.toJsonStr(configuration);
    }


    @Value("${ChannelConfig.foo.name}")
    private String fooChannelName;

    @Value("${ChannelConfig.bar.name}")
    private String barChannelName;



    @Value("${ChainCode.trans.filePath}")
    private String chainCodeFilePath;
    @Value("${ChainCode.trans.path}")
    private String chainCodePath;
    @Value("${ChainCode.trans.name}")
    private String chainCodeName;
    @Value("${ChainCode.trans.version}")
    private String chainCodeVersion;

    private ChaincodeID chaincodeID;

    private Collection<ProposalResponse> instantiateSuccessful;

    @Value("${ChainCode.CollectionConfiguration}")
    private String collectionConfiguration;

    @RequestMapping("install")
    public String install() throws org.hyperledger.fabric.sdk.exception.InvalidArgumentException, IOException, ClassNotFoundException, ProposalException {

        HFClient client = FabricManager.newClient();
        install(client,fooChannelName);
//        install(client,barChannelName);

        return "4 install cc 成功！";
    }

    private void install(HFClient client,String channelName)
            throws IOException, ClassNotFoundException, org.hyperledger.fabric.sdk.exception.InvalidArgumentException,
                   ProposalException {
        Channel channel = FabricManager.getSampleStore().getChannel(client, channelName);

        Collection<Orderer> orderers = channel.getOrderers();
        Collection<ProposalResponse> responses;
        Collection<ProposalResponse> successful = new LinkedList<>();
        Collection<ProposalResponse> failed = new LinkedList<>();

        // Register a chaincode event listener that will trigger for any chaincode id and only for EXPECTED_EVENT_NAME event.

        String chaincodeEventListenerHandle = channel.registerChaincodeEventListener(Pattern.compile(".*"),
                                                                                     Pattern.compile(Pattern.quote("event")),
                                                                                     (handle, blockEvent, chaincodeEvent) -> {

                    String es = blockEvent.getPeer() != null ? blockEvent.getPeer().getName() : blockEvent.getEventHub().getName();
                    log.info("RECEIVED Chaincode event with handle: %s, chaincode Id: %s, chaincode event name: %s, "
                                    + "transaction id: %s, event payload: \"%s\", from eventhub: %s",
                            handle, chaincodeEvent.getChaincodeId(),
                            chaincodeEvent.getEventName(),
                            chaincodeEvent.getTxId(),
                            new String(chaincodeEvent.getPayload()), es);

                });

        //TODO 以参数形式传递进来
        chaincodeID = ChaincodeID.newBuilder().setName(chainCodeName)
                                 .setVersion(chainCodeVersion)
                                 .setPath(chainCodePath).build();

        ////////////////////////////
        // Install Proposal Request
        //
        //TODO peerOrg1
        FabricConfiguration.Organization organization = configuration.getOrganizations().get("peerOrg1");
        client.setUserContext(organization.getPeerAdmin());

        log.info("Creating install proposal");

        InstallProposalRequest installProposalRequest = client.newInstallProposalRequest();
        installProposalRequest.setChaincodeID(chaincodeID);

        ////For GO language and serving just a single user, chaincodeSource is mostly likely the users GOPATH
        installProposalRequest.setChaincodeSourceLocation(Paths.get(chainCodeFilePath).toFile());

        installProposalRequest.setChaincodeVersion(chainCodeVersion);
        installProposalRequest.setChaincodeLanguage(TransactionRequest.Type.GO_LANG);

        log.info("Sending install proposal");

        ////////////////////////////
        // only a client from the same org as the peer can issue an install request
        int numInstallProposal = 0;
        //    Set<String> orgs = orgPeers.keySet();
        //   for (SampleOrg org : testSampleOrgs) {

        Collection<Peer> peers = channel.getPeers();
        numInstallProposal = numInstallProposal + peers.size();

        //grpc 调用
        responses = client.sendInstallProposal(installProposalRequest, peers);

        for (ProposalResponse response : responses) {
            if (response.getStatus() == ProposalResponse.Status.SUCCESS) {
                log.info("Successful install proposal response Txid: {} from peer {}", response.getTransactionID(), response.getPeer().getName());
                successful.add(response);
            } else {
                failed.add(response);
            }
        }

        //   }
        log.info("Received {} install proposal responses. Successful+verified: {} . Failed: {}", numInstallProposal, successful.size(), failed.size());

        if (failed.size() > 0) {
            ProposalResponse first = failed.iterator().next();
            log.error("Not enough endorsers for install :" + successful.size() + ".  " + first.getMessage());
        }
    }



    @RequestMapping("instantiate")
    public String instantiate() throws org.hyperledger.fabric.sdk.exception.InvalidArgumentException, IOException, ClassNotFoundException, ChaincodeCollectionConfigurationException, ProposalException {

        HFClient client = FabricManager.newClient();
        Channel channel = FabricManager.getSampleStore().getChannel(client, fooChannelName);


        ///////////////
        //// Instantiate chaincode.
        System.out.println(" 实例化chaincode .... ");
        InstantiateProposalRequest instantiateProposalRequest = client.newInstantiationProposalRequest();
        instantiateProposalRequest.setProposalWaitTime(configuration.getConfig().getProposalWaitTime());
        instantiateProposalRequest.setChaincodeID(chaincodeID);
        instantiateProposalRequest.setChaincodeLanguage(TransactionRequest.Type.GO_LANG);
        instantiateProposalRequest.setFcn("init");
        instantiateProposalRequest.setArgs(new String[] {});
        instantiateProposalRequest.setChaincodeCollectionConfiguration(
                ChaincodeCollectionConfiguration.fromYamlFile(new File(collectionConfiguration)));

        log.info("Sending instantiate proposal");

        Collection<ProposalResponse> responses = channel.sendInstantiationProposal(instantiateProposalRequest);
        final Collection<ProposalResponse> successful = new LinkedList<>();
        final Collection<ProposalResponse> failed = new LinkedList<>();

        for (ProposalResponse response : responses) {
            if (response.getStatus() == ChaincodeResponse.Status.SUCCESS) {
                log.info("Successful upgrade proposal response Txid: {} from peer {}", response.getTransactionID(), response.getPeer().getName());
                successful.add(response);
            } else {
                failed.add(response);
            }
        }

        if (failed.size() > 0) {
            ProposalResponse first = failed.iterator().next();
            log.error("Not enough endorsers for upgrade :"
                    + successful.size() + ".  " + first.getMessage());
        }

        instantiateSuccessful= new LinkedList(successful);


        return "5 实例化 cc 成功！";
    }


    @RequestMapping("/save")
    public String save()
            throws org.hyperledger.fabric.sdk.exception.InvalidArgumentException, IOException, ClassNotFoundException {

        log.info("Sending instantiate proposal to orderer.");
        HFClient client = FabricManager.newClient();
        Channel channel = FabricManager.getSampleStore().getChannel(client, fooChannelName);


        FabricConfiguration.Organization organization = configuration.getOrganizations().get("peerOrg1");

        final Collection<ProposalResponse> successful = new LinkedList<>();
        final Collection<ProposalResponse> failed = new LinkedList<>();

        channel.sendTransaction(instantiateSuccessful, organization.getPeerAdmin()).thenApply(transactionEvent -> {
            try {
                log.info("instantiate proposal completed.");

                //Now lets run the new chaincode which should *double* the results we asked to move.
                // return setAmount(client, channel, chaincodeID, "50", null).get(testConfig.getTransactionWaitTime(), TimeUnit.SECONDS);


                try {
//                    Collection<ProposalResponse> successful = new LinkedList<>();
//                    Collection<ProposalResponse> failed = new LinkedList<>();
                    successful.clear();
                    failed.clear();
                    ///////////////
                    /// Send transaction proposal to all peers
                    TransactionProposalRequest transactionProposalRequest = client.newTransactionProposalRequest();
                    transactionProposalRequest.setChaincodeID(chaincodeID);
                    transactionProposalRequest.setFcn("save");
                    transactionProposalRequest.setArgs(new String[]{"{ \"BankCardNo\": \"62212346789\", \"CusName\": \"萨摩耶\", \"IdNo\": \"123456\", \"MobilePhone\": \"1300501164\", \"Amount\": \"12.00\", \"CusNo\": \"123456\" }"});

                    transactionProposalRequest.setProposalWaitTime(configuration.getConfig().getProposalWaitTime());
//                    if (user != null) { // specific user use that
//                        transactionProposalRequest.setUserContext(user);
//                    }

                    Collection<ProposalResponse> invokePropResp = channel.sendTransactionProposal(transactionProposalRequest);
                    for (ProposalResponse response : invokePropResp) {
                        if (response.getStatus() == ChaincodeResponse.Status.SUCCESS) {
                            log.info("Successful transaction proposal response Txid: %s from peer %s", response.getTransactionID(), response.getPeer().getName());
                            successful.add(response);
                        } else {
                            failed.add(response);
                        }
                    }

                    log.info("Received {} transaction proposal responses for setAmount. Successful+verified: {} . Failed: {}",
                             invokePropResp.size(), successful.size(), failed.size());
                    if (failed.size() > 0) {
                        ProposalResponse firstTransactionProposalResponse = failed.iterator().next();

                        throw new ProposalException(format("Not enough endorsers for set(move a,b,%s):%d endorser error:%s. Was verified:%b",
                                                           0, firstTransactionProposalResponse.getStatus().getStatus(), firstTransactionProposalResponse.getMessage(), firstTransactionProposalResponse.isVerified()));

                    }
                    log.info("Successfully received transaction proposal responses for setAmount. Now sending to orderer.");

                    ////////////////////////////
                    CompletableFuture<BlockEvent.TransactionEvent> transactionEventCompletableFuture = channel.sendTransaction(successful);
                    return transactionEventCompletableFuture.get(configuration.getConfig().getOrdererWaitTime(), TimeUnit.SECONDS);
                } catch (Exception e) {

                    throw new CompletionException(e);

                }

            } catch (CompletionException e) {
                return e;
            } catch (Exception e) {
                return new CompletionException(e);
            }
        });

        return "保存数据成功!";
    }

    @RequestMapping("query")
    public String query() throws org.hyperledger.fabric.sdk.exception.InvalidArgumentException, IOException, ClassNotFoundException {
        HFClient client = FabricManager.newClient();
        Channel channel = FabricManager.getSampleStore().getChannel(client, fooChannelName);

        log.info("Now query chaincode {} on channel {} ", chaincodeID, channel.getName());
        QueryByChaincodeRequest queryByChaincodeRequest = client.newQueryProposalRequest();
        // test using bytes as args. End2end uses Strings.
        queryByChaincodeRequest.setArgs("123456".getBytes(UTF_8));
        queryByChaincodeRequest.setFcn("query");
        queryByChaincodeRequest.setChaincodeID(chaincodeID);

        Collection<ProposalResponse> queryProposals;

        try {
            queryProposals = channel.queryByChaincode(queryByChaincodeRequest);
        } catch (Exception e) {
            throw new CompletionException(e);
        }

        for (ProposalResponse proposalResponse : queryProposals) {
            if (!proposalResponse.isVerified() || proposalResponse.getStatus() != ChaincodeResponse.Status.SUCCESS) {
                log.error("Failed query proposal from peer " + proposalResponse.getPeer().getName() + " status: " + proposalResponse.getStatus() +
                        ". Messages: " + proposalResponse.getMessage()
                        + ". Was verified : " + proposalResponse.isVerified());
            } else {
                String payload = proposalResponse.getProposalResponse().getResponse().getPayload().toStringUtf8();
                log.info("Query payload of b from peer {} returned {}", proposalResponse.getPeer().getName(), payload);
            }
        }

        return "6 查询结果成功！";
    }

}
